package cn.wolfcode.service.impl;

import cn.wolfcode.domain.AccountLog;
import cn.wolfcode.domain.AccountTransaction;
import cn.wolfcode.domain.OperateIntergralVo;
import cn.wolfcode.domain.RefundVo;
import cn.wolfcode.mapper.AccountLogMapper;
import cn.wolfcode.mapper.AccountTransactionMapper;
import cn.wolfcode.mapper.UsableIntegralMapper;
import cn.wolfcode.service.IUsableIntegralService;
import cn.wolfcode.util.AssertUtils;
import cn.wolfcode.util.IdGenerateUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import io.seata.rm.tcc.api.BusinessActionContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.Date;


@Slf4j
@Service
public class UsableIntegralServiceImpl implements IUsableIntegralService {
    private final IdGenerateUtil idGenerateUtil;
    private final UsableIntegralMapper usableIntegralMapper;
    private final AccountTransactionMapper accountTransactionMapper;
    private final AccountLogMapper accountLogMapper;

    public UsableIntegralServiceImpl(UsableIntegralMapper usableIntegralMapper, AccountTransactionMapper accountTransactionMapper, AccountLogMapper accountLogMapper, IdGenerateUtil idGenerateUtil) {
        this.usableIntegralMapper = usableIntegralMapper;
        this.accountTransactionMapper = accountTransactionMapper;
        this.accountLogMapper = accountLogMapper;
        this.idGenerateUtil = idGenerateUtil;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public String tryPayment(OperateIntergralVo vo, BusinessActionContext ctx) {
        log.info("[TCC-TRY] 收到请求参数: {}, 准备执行 tryPayment", JSON.toJSONString(vo));
        // 1. 实现防悬挂, 直接向 MySQL 插入事务控制记录
        String tradeNo = idGenerateUtil.nextId() + "";
        this.insertTxLog(vo, ctx, tradeNo, AccountLog.TYPE_DECR, AccountTransaction.STATE_TRY);
        // 2. 冻结积分, 判断是否冻结成功
        int row = usableIntegralMapper.freezeIntergral(vo.getUserId(), vo.getValue());
        AssertUtils.isTrue(row > 0, "账户余额不足");
        return tradeNo;
    }

    private void insertTxLog(OperateIntergralVo vo, BusinessActionContext ctx, String tradeNo, Integer type, Integer state) {
        AccountTransaction tx = new AccountTransaction();
        tx.setAmount(vo.getValue());
        tx.setType(type);
        tx.setState(state);
        Date now = new Date();
        tx.setGmtCreated(now);
        tx.setTradeNo(tradeNo);
        tx.setGmtModified(now);
        tx.setUserId(vo.getUserId());
        tx.setTxId(ctx.getXid());
        tx.setActionId(ctx.getBranchId());
        // 保存事务记录
        accountTransactionMapper.insert(tx);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void commitPayment(BusinessActionContext ctx) {
        Object obj = ctx.getActionContext("vo");
        log.info("[TCC-CONFIRM] 收到 TCC CONFIRM 消息 xid={}, branchId={}, 准备执行业务并提交本地事务: {}", ctx.getXid(), ctx.getBranchId(), obj);
        JSONObject vo = (JSONObject) obj;

        // 1. 基于全局事务id+分支事务id获取唯一的事务记录
        AccountTransaction tx = accountTransactionMapper.get(ctx.getXid(), ctx.getBranchId());
        // 2. 判断是否为空, 如果为空, 说明一阶段都没执行过, 流程异常
        if (tx == null) {
            log.warn("[TCC-CONFIRM] 流程异常, 无法查询到 TRY 阶段事务记录, 终止 CONFIRM 流程 xid={}, branchId={}", ctx.getXid(), ctx.getBranchId());
            return;
        }
        // 3. 判断状态是否为已提交, 如果是, 说明已经提交过了, 保证幂等即可
        if (AccountTransaction.STATE_COMMIT == tx.getState()) {
            log.warn("[TCC-CONFIRM] 流程异常, CONFIRM 方法被重复调用 xid={}, branchId={}", ctx.getXid(), ctx.getBranchId());
            return;
        } else if (AccountTransaction.STATE_CANCEL == tx.getState()) {
            // 4. 判断状态是否为已回滚, 如果是, 说明流程异常, 方法终止
            log.warn("[TCC-CONFIRM] 流程异常, 已经执行过 CANCEL, 无法再执行 CONFIRM xid={}, branchId={}", ctx.getXid(), ctx.getBranchId());
            return;
        }

        // 5. 真正扣除金额(扣除冻结金额+账户余额)
        usableIntegralMapper.commitChange(vo.getLong("userId"), vo.getLong("value"));
        // 6. 创建交易流水日志, 保存到数据库
        AccountLog accountLog = new AccountLog();
        accountLog.setAmount(vo.getLong("value"));
        accountLog.setInfo(vo.getString("info"));
        accountLog.setGmtTime(new Date());
        accountLog.setOutTradeNo(vo.getString("outTradeNo"));
        // 从上下文中获取交易流水号
        accountLog.setTradeNo(tx.getTradeNo());
        accountLog.setType(AccountLog.TYPE_DECR);
        accountLog.setUserId(vo.getLong("userId"));
        accountLogMapper.insert(accountLog);

        // 将事务记录状态更新为已提交
        int row = accountTransactionMapper.updateAccountTransactionState(
                ctx.getXid(),
                ctx.getBranchId(),
                AccountTransaction.STATE_COMMIT,
                AccountTransaction.STATE_TRY
        );
        log.info("[TCC-CONFIRM] 积分支付提交操作执行完毕: {}", row);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void rollbackPayment(BusinessActionContext ctx) {
        Object obj = ctx.getActionContext("vo");
        log.info("[TCC-CANCEL] 收到 TCC CANCEL 消息 xid={}, branchId={}, 准备回滚 TRY 阶段操作: {}", ctx.getXid(), ctx.getBranchId(), obj);
        JSONObject vo = (JSONObject) obj;

        // 1. 先查询事务记录, 判断是否为空(空回滚)
        AccountTransaction tx = accountTransactionMapper.get(ctx.getXid(), ctx.getBranchId());
        // 2. 如果为空, 插入一条 CANCEL 类型的事务记录, 并终止方法
        if (tx == null) {
            try {
                log.warn("[TCC-CANCEL] 流程异常, 出现空回滚问题, TRY 阶段未执行, 直接执行 CANCEL xid={}, branchId={}", ctx.getXid(), ctx.getBranchId());
                this.insertTxLog(vo.toJavaObject(OperateIntergralVo.class), ctx, null, AccountLog.TYPE_DECR, AccountTransaction.STATE_CANCEL);
            } catch (Exception e) {
                log.warn("[TCC-CANCEL] 流程异常, 出现空回滚并发问题, 在插入空回滚记录前 TRY 先执行了 xid={}, branchId={}", ctx.getXid(), ctx.getBranchId());
                log.error("[TCC-CANCEL] 打印流程异常信息", e);
            }
            // 出现空回滚以后, 无论是否出现异常, 都应该直接结束方法
            return;
        }
        // 3. 如果不为空, 判断事务状态是否为已回滚, 如果是说明重复执行回滚操作, 直接结束方法
        if (tx.getState() == AccountTransaction.STATE_CANCEL) {
            log.warn("[TCC-CANCEL] 流程异常, 出现回滚重复执行问题, 直接结束方法 xid={}, branchId={}", ctx.getXid(), ctx.getBranchId());
            return;
        } else if (tx.getState() == AccountTransaction.STATE_COMMIT) {
            // 4. 否则判断状态是否为已提交, 如果是, 说明流程异常, 结束方法
            log.warn("[TCC-CANCEL] 流程异常, 已经执行过 CONFIRM, 不允许再执行 CANCEL 方法 xid={}, branchId={}", ctx.getXid(), ctx.getBranchId());
            return;
        }
        // 5. 如果是初始化方法, 正常流程, 将之前 TRY 执行的冻结金额回滚回去
        usableIntegralMapper.unFreezeIntergral(vo.getLong("userId"), vo.getLong("value"));

        // 6. 修改事务记录状态为已回滚
        int row = accountTransactionMapper.updateAccountTransactionState(
                ctx.getXid(),
                ctx.getBranchId(),
                AccountTransaction.STATE_CANCEL,
                AccountTransaction.STATE_TRY
        );

        log.info("[TCC-CANCEL] 更新事务记录状态为已回滚: {}", row);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public String doPay(OperateIntergralVo vo) {
        // 1. 直接使用当前需要扣除的积分扣除用户积分, 增加判断条件为剩余积分>=要扣除的积分, 不满足条件说明积分不足
        int row = usableIntegralMapper.decrIntegral(vo.getUserId(), vo.getValue());
        AssertUtils.isTrue(row > 0, "账户积分不足");

        // 2. 构建账户流水日志, 并保存
        AccountLog log = new AccountLog();
        log.setAmount(vo.getValue());
        log.setInfo(vo.getInfo());
        log.setGmtTime(new Date());
        log.setOutTradeNo(vo.getOutTradeNo());
        log.setTradeNo(idGenerateUtil.nextId() + "");
        log.setType(AccountLog.TYPE_DECR);
        log.setUserId(vo.getUserId());
        accountLogMapper.insert(log);
        return log.getTradeNo();
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean doRefund(RefundVo refundVo) {
        // 1. 基于订单编号查询支付流水, 类型必须是扣款类型
        AccountLog decrLog = accountLogMapper.selectByOutTradeNoAndType(refundVo.getOutTradeNo(), AccountLog.TYPE_DECR);
        AssertUtils.notNull(decrLog, "退款失败, 该订单还未支付");
        // 2. 通过支付流水中支付的金额, 将其退回到用户积分账户
        BigDecimal refundAmount = new BigDecimal(refundVo.getRefundAmount());
        int ret = refundAmount.compareTo(new BigDecimal(decrLog.getAmount()));
        AssertUtils.isTrue(ret <= 0, "退款金额不能大于支付金额");

        usableIntegralMapper.addIntergral(decrLog.getUserId(), refundAmount.longValue());

        // 3. 记录新的退款流水记录
        AccountLog incrLog = new AccountLog();
        incrLog.setInfo(refundVo.getRefundReason());
        incrLog.setType(AccountLog.TYPE_INCR);
        incrLog.setAmount(refundAmount.longValue());
        incrLog.setTradeNo(idGenerateUtil.nextId() + "");
        incrLog.setOutTradeNo(refundVo.getOutTradeNo());
        incrLog.setUserId(decrLog.getUserId());
        incrLog.setGmtTime(new Date());
        accountLogMapper.insert(incrLog);
        return true;
    }
}
